const parse = require('@babel/parser').parse
const generator = require('@babel/generator').default;
const traverse = require('@babel/traverse').default;
const types = require('@babel/types')
const fs = require('fs')
const path = require('path');

// 自行将后面讲的三个特征的代码放到这

// 待反混淆的文件
let jsCode = fs.readFileSync(path.resolve(__dirname, 'encode.js'), { encoding: 'utf-8' });

let ast = parse(jsCode);

//TODO 1 解密三部分的代码执行
let end = 4;//切片需要处理的代码块
let newAst = parse('');//新建ast
let decrypt_code = ast.program.body.slice(0, end);//切片
newAst.program.body = decrypt_code// 将前3个节点替换进新建ast
let stringDecryptFunc = generator(newAst, { compact: true },).code;//转为js，由于存在格式化检测，需要指定选项，来压缩代码// 自动转义
eval(stringDecryptFunc);//执行三部分的代码

let stringDecryptFuncAst = ast.program.body[1];// 拿到解密函数所在的节点
let startFuncName = stringDecryptFuncAst.id.name;//拿到解密函数的名字
console.log("解密函数名称：", startFuncName)

let stringDecryptFuncAst2 = ast.program.body[3];// 拿到解密函数所在的节点
let startFuncName2 = stringDecryptFuncAst2.id.name;//拿到解密函数的名字
console.log("解密函数名称：", startFuncName2)

var rest_code = ast.program.body.slice(end); // 剩下的节点
ast.program.body = rest_code;//剩下的节点替换


decodeFuncArr = [startFuncName, startFuncName2]
decodeObjArr = []
// 递归解密函数的引用，添加到数组中
traverse(ast, {
    VariableDeclarator: function (path) {
        if (
            path.get('id').isIdentifier() &&
            path.get('init').isIdentifier() &&
            decodeFuncArr.indexOf(path.get('init.name').node) != -1
        ) {
            decodeFuncArr.push(path.get('id.name').node)
        }

        if (path.get('id').isIdentifier() && path.get('init').isObjectExpression()
            && path.get('init.properties').length > 0
            && decodeObjArr.indexOf(path.get('id.name').node) == -1) {
            decodeObjArr.push(path.get('id.name').node)
        }
    }
})

console.log(decodeFuncArr)
console.log(decodeObjArr)

let argsType = ['isNumericLiteral']
traverse(ast, {
    CallExpression: {
        exit: function (path) {
            if (
                path.get('callee').isIdentifier() &&
                decodeFuncArr.indexOf(path.get('callee.name').node) != -1 &&
                path.get('arguments').length === 1 && types.isNumericLiteral(path.get('arguments')[0])
            ) {
                // console.log(path.toString())
                let argTypeTagArr = []  // 存储参数是否为指定类型的数组
                for (let i = 0; i < argsType.length; i++) {
                    argTypeTagArr.push(path.get(`arguments.${i}`)[argsType[i]]())
                }

                if (argTypeTagArr.every(c => c)) {
                    // 如果符合指定的类型，就是需要解密的地方
                    let args = []  // 存储参数的值

                    for (let i = 0; i < argsType.length; i++) {
                        args.push(path.get(`arguments.${i}.value`).node)
                    }
                    console.log(`${startFuncName}(${args.join(',')})`)
                    console.log(path.toString(), '-->', eval(`${startFuncName}(${args.join(',')})`))
                    path.replaceWith(types.valueToNode(eval(`${startFuncName}(${args.join(',')})`)))
                }
            }
            if (
                path.get('callee').isIdentifier() &&
                decodeFuncArr.indexOf(path.get('callee.name').node) != -1 &&
                path.get('arguments').length === 1 && types.isStringLiteral(path.get('arguments')[0])
            ) {
                let args = path.get('arguments.0').node.value
                console.log(path.toString(), '-->', eval(`${startFuncName2}('${args}')`))
                path.replaceWith(types.valueToNode(eval(`${startFuncName2}('${args}')`)))
            }
        }
    }
})

// 拼接字符串
traverse(ast, {
    BinaryExpression: {
        exit: function (path) {
            if (path.get("left").isStringLiteral() && path.get("right").isStringLiteral()) {
                let left = path.get("left").node.value
                let right = path.get("right").node.value
                path.replaceInline(types.valueToNode(left + right))
            }
        }
    }
})


let buildInFunc = [
    'apply', 'slice', 'shift', 'which', 'split', 'index', 'input', 'clone', 'token', 'refer', 'scene', 'width',
    'style', 'round', 'parse', 'match', 'catch', 'get', 'enumerable', 'prototype', 'exports', 'value', 'sham',
    'callee', 'constructor', 'length', 'call', 'indexOf', 'map', 'filter', '__proto__', 'src', 'contentWindow',
    'that', 'write', 'close', 'enter', 'error', 'clamp', 'reset', 'floor', 'clear'
]
// 对象中的字符串以及函数调用还原回去
traverse(ast, {
    MemberExpression: {
        exit: function (path) {
            if (
                path.get('object').isIdentifier() &&
                path.get('property').isStringLiteral()
            ) {
                let identifier = path.get('object.name').node
                let property = path.get('property.value').node
                if (property.length !== 5) return
                if (buildInFunc.indexOf(property) !== -1) return
                if (!path.scope.getAllBindings()[identifier]) return
                let property_nodes = path.scope.getAllBindings()[identifier].path.get('init.properties')

                for (let i = 0; i < property_nodes.length; i++) {
                    let obj_property = property_nodes[i].get('key.value').node
                    if (
                        obj_property === property &&
                        property_nodes[i].get('value').isStringLiteral()
                    ) {
                        console.log(path.toString(), '-->', property_nodes[i].get('value.value').node)
                        path.replaceWith(types.valueToNode(property_nodes[i].get('value.value').node))
                    }
                }
            }
            if (path.get('object').isIdentifier() &&
                path.get('property').isIdentifier()) {
                let identifier = path.get('object.name').node
                let property = path.get('property.name').node
                if (buildInFunc.indexOf(property) !== -1) return
                if (decodeObjArr.indexOf(identifier) != -1) {
                    // 如果只是简单的变量，直接取值
                    if (property.length !== 5) {
                        if (path.scope.getAllBindings()[property] && types.isStringLiteral(path.scope.getAllBindings()[property].path.get('init'))) {
                            let obj_property = path.scope.getAllBindings()[property].path.get('init.value').node
                            console.log(path.toString(), '-->', obj_property)
                            path.replaceWith(types.valueToNode(obj_property))
                        }

                    } else {
                        // 否则从对象中取值
                        if (types.isObjectExpression(path.scope.getAllBindings()[identifier].path.get('init'))) {
                            let property_nodes = path.scope.getAllBindings()[identifier].path.get('init.properties')
                            for (let i = 0; i < property_nodes.length; i++) {
                                let obj_property = property_nodes[i].get('key.name').node
                                if (
                                    obj_property === property &&
                                    property_nodes[i].get('value').isNumericLiteral()
                                ) {
                                    console.log(path.toString(), '-->', property_nodes[i].get('value.value').node)
                                    path.replaceWith(types.valueToNode(property_nodes[i].get('value.value').node))
                                }
                            }

                        }
                    }
                }
            }
        }
    }

})


// 从对象中取函数调用还原
traverse(ast, {
    CallExpression: {
        exit: function (path) {
            if (
                path.get('callee').isMemberExpression() &&
                path.get('callee.property').isStringLiteral()
            ) {
                let identifier = path.get('callee.object.name').node
                let property = path.get('callee.property.value').node
                if (property.length !== 5) return
                if (buildInFunc.indexOf(property) !== -1) return

                // 获取obj对象属性值，为操作符或函数
                let property_paths = path.scope.getAllBindings()[identifier].path.get('init.properties')
                property_paths = Array.from(property_paths)
                property_paths.forEach(node_path => {
                    // 属性名称
                    let obj_property = node_path.get('key.value').node
                    if (
                        obj_property === property &&
                        node_path.get('value').isFunctionExpression()
                    ) {
                        let func_bodys = node_path.get('value.body.body')
                        func_bodys = Array.from(func_bodys)
                        func_bodys.forEach(body => {
                            // 在return处才知道函数是操作符类型还是函数调用类型
                            if (body.isReturnStatement()) {
                                if (body.get('argument').isBinaryExpression()) {
                                    // 操作符还原
                                    let operator = body.get('argument.operator').node
                                    let left = path.get('arguments.0')
                                    let right = path.get('arguments.1')
                                    console.log(path.toString(), '-->', left.toString(), operator, right.toString())
                                    path.replaceWith(types.binaryExpression(operator, left.node, right.node))
                                } else if (body.get('argument').isCallExpression()) {
                                    // 函数调用还原
                                    let origin_args = path.get('arguments')
                                    origin_args = Array.from(origin_args)
                                    let args
                                    if (origin_args.length === 1) {
                                        args = []  // 没有参数
                                    } else {
                                        args = origin_args.slice(1).map(arg => arg.node)
                                    }
                                    let old_path_string = path.toString()
                                    path.replaceWith(types.callExpression(origin_args[0].node, args))
                                    console.log(old_path_string, '-->', path.toString())
                                } else if (body.get('argument').isLogicalExpression()) {
                                    // 操作符还原
                                    let operator = body.get('argument.operator').node
                                    let left = path.get('arguments.0')
                                    let right = path.get('arguments.1')
                                    console.log(path.toString(), '-->', left.toString(), operator, right.toString())
                                    path.replaceWith(types.logicalExpression(operator, left.node, right.node))
                                }
                            }
                        })
                    }
                })
            }

            if (path.get('callee').isMemberExpression() &&
                path.get('callee.property').isIdentifier()) {
                let identifier = path.get('callee.object.name').node
                let property = path.get('callee.property.name').node

                if (identifier == undefined) return
                if (property.length !== 5) return
                if (buildInFunc.indexOf(property) !== -1) return

                // 获取obj对象属性值，为操作符或函数
                // 需要引用的对象中的变量
                if (path.scope.getAllBindings()[identifier] && !types.isObjectExpression(path.scope.getAllBindings()[identifier].path.get('init'))) return

                // 获取obj对象属性值，为操作符或函数
                let property_paths = path.scope.getAllBindings()[identifier].path.get('init.properties')
                property_paths = Array.from(property_paths)
                property_paths.forEach(node_path => {
                    // 属性名称
                    let obj_property = node_path.get('key.value').node
                    if (
                        obj_property === property &&
                        node_path.get('value').isFunctionExpression()
                    ) {
                        let func_bodys = node_path.get('value.body.body')
                        func_bodys = Array.from(func_bodys)
                        func_bodys.forEach(body => {
                            // 在return处才知道函数是操作符类型还是函数调用类型
                            if (body.isReturnStatement()) {
                                if (body.get('argument').isBinaryExpression()) {
                                    // 操作符还原
                                    let operator = body.get('argument.operator').node
                                    let left = path.get('arguments.0')
                                    let right = path.get('arguments.1')
                                    console.log(path.toString(), '-->', left.toString(), operator, right.toString())
                                    path.replaceWith(types.binaryExpression(operator, left.node, right.node))
                                } else if (body.get('argument').isCallExpression()) {
                                    // 函数调用还原
                                    let origin_args = path.get('arguments')
                                    origin_args = Array.from(origin_args)
                                    let args
                                    if (origin_args.length === 1) {
                                        args = []  // 没有参数
                                    } else {
                                        args = origin_args.slice(1).map(arg => arg.node)
                                    }
                                    let old_path_string = path.toString()
                                    path.replaceWith(types.callExpression(origin_args[0].node, args))
                                    console.log(old_path_string, '-->', path.toString())
                                } else if (body.get('argument').isLogicalExpression()) {
                                    // 操作符还原
                                    let operator = body.get('argument.operator').node
                                    let left = path.get('arguments.0')
                                    let right = path.get('arguments.1')
                                    console.log(path.toString(), '-->', left.toString(), operator, right.toString())
                                    path.replaceWith(types.logicalExpression(operator, left.node, right.node))
                                }
                            }
                        })
                    }
                })
            }
        }
    }
})

// 四则运算表达式还原
traverse(ast, {
    BinaryExpression: {
        exit: function (path) {
            // 跳过不正常的计算
            if(path.toString() == '1 / 0') return
            let left, right, operator;
            if (path.get("left").isUnaryExpression() && path.get("right").isNumericLiteral()) {
                operator = path.get("operator").node
                left = Number(path.get("left.operator").node + path.get("left.argument").node.value)
                right = path.get("right").node.value
            }
            if (path.get("left").isUnaryExpression() && path.get("right").isUnaryExpression()) {
                operator = path.get("operator").node
                left = Number(path.get("left.operator").node + path.get("left.argument").node.value)
                right = Number(path.get("right.operator").node + path.get("right.argument").node.value)
            }
            if (path.get("left").isNumericLiteral() && path.get("right").isNumericLiteral()) {
                operator = path.get("operator").node
                left = path.get("left").node.value
                right = path.get("right").node.value
            }
            if (path.get("left").isNumericLiteral() && path.get("right").isUnaryExpression()) {
                operator = path.get("operator").node
                left = path.get("left").node.value
                right = Number(path.get("right.operator").node + path.get("right.argument").node.value)
            }
            //let result = numCalculation(operator, left, right)
            if(left !== undefined && right !== undefined){
                let result = eval(`${left} ${operator} ${right}`)
                console.log(path.toString(), '-->', result)
                path.replaceInline(types.valueToNode(result))
            }
        }
    }
})

// 控制流进行还原
let controler_code = {}
let controler = {}
traverse(ast, {
    WhileStatement: {
        exit: function (path) {
            if (
                path.get('test').isUnaryExpression() || (path.get('test').isArrayExpression() && path.get('test').toString() === '[]')
            ) {
                if (path.get('body.body').length === 0) return  // while循环体为空，直接返回
                if (path.get('body.body.0').isTryStatement()) return
                //console.log(path.toString())
                let switch_condition
                try {
                    switch_condition = path.get('body.body.0.discriminant.object.name').node  // 控制器名称
                } catch (e) {
                    return
                }
                controler_code[switch_condition] = {}  // 整体代码有多个控制流，需要分开

                if (!path.scope.getAllBindings()[switch_condition].path.get('init.callee.object').isStringLiteral()) return
                // 取控制器，var _0x41a9c6 = "1|4|3|0|2"["split"]('|')
                eval(`controler['${switch_condition}'] = ` + path.scope.getAllBindings()[switch_condition].path.get('init').toString())
                let cases_path = path.get('body.body.0.cases')  // 拿到所有case节点，数组类型
                for (var i = 0; i < cases_path.length; i++) {
                    let case_num = cases_path[i].get('test.value').node  // case的值
                    controler_code[switch_condition][case_num] = []  // 控制流的代码
                    let case_content = cases_path[i].get('consequent')  // case的内容
                    case_content = Array.from(case_content)
                    case_content.forEach(c => {
                        if (!c.isContinueStatement()) {
                            // 剔除case中的continue
                            controler_code[switch_condition][case_num].push(c)
                        }
                    })
                }
                let code_node = []
                for (var i = 0; i < controler[switch_condition].length; i++) {
                    let index = controler[switch_condition][i]
                    controler_code[switch_condition][index].forEach(n => {
                        code_node.push(n.node)
                    })
                    // code_node.push(controler_code[switch_condition][index][0].node)
                }
                path.replaceWithMultiple(code_node)
            }
        }
    },
    ForStatement: {
        exit: function (path) {
            if (!path.get('body').isBlockStatement() || path.node.body.body.length < 1 || !types.isSwitchStatement(path.node.body.body[0])) return;
            const switchStatement = path.node.body.body[0];
            if (!types.isMemberExpression(switchStatement.discriminant)) return;

            // 找到discriminant变量的binding, 从而获得初始值.
            let stepVarBinding = path.scope.getBinding(switchStatement.discriminant.object.name).path;
            let stepVar;
            while (true) {
                // 一直查找到初始化值的地方
                if (types.isVariableDeclarator(stepVarBinding)
                    && types.isArrayExpression(stepVarBinding.get('init'))) {
                    stepVar = stepVarBinding.node.init.elements;
                    break;
                }
                stepVarBinding = path.scope.getBinding(stepVarBinding.get('init.name').node).path;
            }
            // 拿到步骤起始点
            let argsName = switchStatement.discriminant.property.argument.name
            let startStep = path.scope.getBinding(argsName).path.get('init');
            let steps = []
            for (let i = startStep; i < stepVar.length; i++) {
                const element = stepVar[i].value;
                steps.push(element)
            }

            // 那case标签
            const cases = {};
            for (const c of switchStatement.cases) {
                cases[c.test.value] = c;
            }
            const blocks = [];

            let length = switchStatement.cases.length
            let count = 0

            for (const step of steps) {
                if (step == undefined) continue;
                if (count >= length) break;
                const switchCase = cases[step]
                if (switchCase == undefined) continue;

                let consequent = switchCase.consequent;
                const lastNode = consequent[consequent.length - 1];
                count += 1;
                // 去除continue
                if (types.isContinueStatement(lastNode) || types.isBreakStatement(lastNode)) {
                    consequent = consequent.slice(0, consequent.length - 1);
                }
                blocks.push(...consequent);
            }

            if (blocks.length > 0) {
                path.replaceWithMultiple(blocks);
            }

        }
    }
})



// 解编码
const transform_literal = {
    NumericLiteral({ node }) {
        if (node.extra && /^0[obx]/i.test(node.extra.raw)) {
            node.extra = undefined;
        }
    },
    StringLiteral({ node }) {
        if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {
            node.extra = undefined;
        }
    }
}
traverse(ast, transform_literal)


//去掉无效引用
// ast = parse(generator(ast, { compact: true }).code)
// traverse(ast, {
//     VariableDeclarator: {
//         exit(path) {
//             let { init, id } = path.node;
//             if (!types.isObjectExpression(init) && !types.isIdentifier(id)) return;
//             let { scope } = path;
//             let binding = scope.getBinding(id.name);
//             if (binding.referencePaths.length !== 0) return;
//             path.remove();
//         }
//     }
// })

function numCalculation(operator, left, right) {
    let result = undefined
    switch (operator) {
        case '+':
            result = left + right;
            break;
        case '-':
            result = left - right;
            break;
        case '*':
            result = left * right;
            break;
        case '/':
            result = left / right;
            break;
        case '!=':
            result = left != right;
            break;
        default:
            console.log("无效运算符", operator)
    }
    return result
}

function findInitObj(path, name) {
    const stepVarBinding = path.scope.getBinding(name).path;
    if (types.isVariableDeclarator(stepVarBinding) && types.isArrayExpression(stepVarBinding.get('init'))) {
        return stepVarBinding.node.init.elements;
    }
    return findInitObj(stepVarBinding, stepVarBinding.get('init.name').node)
}



// 语法数转JS代码
let { code } = generator(ast, { compact: false });

// 保存
fs.writeFile(path.resolve(__dirname, './decode.js'), code, (err) => {
});